/**
 * 
 */
package truerefactor.refactor;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;

/**
 * EncapsulateField - An Implementation of the Encapsulate Field Refactoring
 * technique
 * 
 * @author Isaac
 */
public class EncapsulateField extends Refactoring {

    /**
     * Creates a new instance of EncapsulateField
     */
    public EncapsulateField()
    {
        this.name = "Encapsulate Field";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        if (operands.isEmpty())
            return;

        AttributeNode field = (AttributeNode) operands.get(0);

        // 1. Create getting and setting methods for the field.
        MethodNode getter = new MethodNode("get" + field.getIdentifier(), "", Accessibility.Public);
        MethodNode setter = new MethodNode("set" + field.getIdentifier(), "", Accessibility.Public);

        ClassNode parent = field.getParent();
        if (parent != null) // FIXME: should never be null
        {
            setupMethods(graph, field, getter, setter, parent);

            replaceReferences(field, getter, setter);

            // FIXME: Assume that this will be handled later when code
            // generation is to be used

            // 3. One all clients are changed, declare the field as private.
            field.setAccessibility(Accessibility.Private);
        }
    }

    private void setupMethods(CodeGraph graph, AttributeNode field, MethodNode getter, MethodNode setter,
            ClassNode parent)
    {
        boolean hasGetter = false;
        boolean hasSetter = false;

        
        for (CodeNode node : parent.getAttributes())
        {
            if (node.getIdentifier().equals(getter.getIdentifier()))
            {
                hasGetter = true;
            }

            if (node.getIdentifier().equals(setter.getIdentifier()))
            {
                hasSetter = true;
            }
        }

        if (!hasGetter)
        {
            graph.insert(getter);
            graph.insert(new Edge(parent, getter, EdgeType.Method));
            getter.addAssociatedAttribute(field);
            field.addAssociatedClassMethod(getter);
            getter.setParentClass(parent);
        }

        if (!hasSetter)
        {
            graph.insert(setter);
            graph.insert(new Edge(parent, setter, EdgeType.Method));
            setter.addAssociatedAttribute(field);
            field.addAssociatedClassMethod(setter);
            setter.setParentClass(parent);
        }
    }

    private void replaceReferences(AttributeNode field, MethodNode getter, MethodNode setter)
    {
        // 2. Find all clients outside the class that reference the field.
        // If the client uses the value, replace the reference with a call to
        // the getting method. If the client changes the value, replace the
        // reference with a call to the setting method.
        // a. If the field is an object and the client invokes a modifier on
        // the object, that is a use. Only use the setting method to replace an
        // assignment.
        for (MethodNode other : field.getOtherMethods())
        {
            double val = Math.random();
            if (val < .5)
            {
                other.addOtherMethod(getter);
            }
            else
            {
                other.addOtherMethod(setter);
            }

            other.removeAssociatedAttribute(field);
        }

        field.getOtherMethods().clear();
        
        field.addAssociatedClassMethod(setter);
        field.addAssociatedClassMethod(getter);
    }
}
